package fr.unice.platdujour.tests;

import static org.junit.Assert.*;

import java.net.MalformedURLException;
import java.nio.channels.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.junit.Test;

import fr.unice.platdujour.application.DataGenerator;
import fr.unice.platdujour.application.GuideMichelin;
import fr.unice.platdujour.application.GuideMichelinImpl;
import fr.unice.platdujour.chord.Identifier;
import fr.unice.platdujour.chord.Key;
import fr.unice.platdujour.chord.Peer;
import fr.unice.platdujour.chord.PeerImpl;
import fr.unice.platdujour.chord.Tracker;
import fr.unice.platdujour.chord.TrackerImpl;
import fr.unice.platdujour.exceptions.AlreadyRegisteredException;

/**
 * Test to check if the data are really reinjected in the network
 * after the death of a peer
 */
public class Test3 {
	
	private static Tracker tracker;

	private static final int NB_PEERS = 5; 
	private static final int RMI_REGISTRY_PORT = 1099;

	/**
	 * Method testing the reinjectData method and the findMissingData method in PeerImpl.
	 * After killing a peer, findMissingData will make an HashMap with the missing data (< R+1).
	 * reinjectData will reinject the data in the network at the right peer.
	 * @throws RemoteException
	 * @throws MalformedURLException
	 * @throws AlreadyBoundException
	 * @throws NotBoundException
	 * @throws AlreadyRegisteredException
	 * @throws InterruptedException
	 * @throws java.rmi.AlreadyBoundException
	 */
	@Test
	public void testReinjectionData() throws RemoteException, MalformedURLException, AlreadyBoundException, NotBoundException, AlreadyRegisteredException, InterruptedException, java.rmi.AlreadyBoundException {
		
		new TrackerImpl(RMI_REGISTRY_PORT);

		tracker =
				(Tracker) Naming.lookup("rmi://localhost:" + RMI_REGISTRY_PORT
						+ "/tracker");
		
		// A Chord network is initialized
		createNetwork(tracker);
		
		// All the peers in the network are listed
		Thread.sleep((long) (Math.log(NB_PEERS)*1000));
		System.out.println("\nTurn around after first stabilization");
		turnAround(tracker.getRandomPeer());
				
		Thread.sleep((long) (Math.log(NB_PEERS)*1000));
		System.out.println("\nTurn around after second stabilization");
		turnAround(tracker.getRandomPeer());
		
		// A GuideMichelin is created. It will use the Chord network
		GuideMichelin guideMichelin = new GuideMichelinImpl(tracker);
		
		// Some data are added to the {@link GuideMichelin}
		DataGenerator dataGenerator = new DataGenerator(10);
		Map<String, String> newData;
		
		for (int i = 0 ; i < 10 ; i++) {
			newData = dataGenerator.getNewData();      
			for (Entry<String, String> entry : newData.entrySet()) {
				guideMichelin.put(entry.getKey(), entry.getValue());
			}
		}
		
		// The peers are listed again with the data they store
		Thread.sleep(2000);
		System.out.println("\nTurn around after adding data");
		turnAround(tracker.getRandomPeer());
		
		Peer peer = tracker.getRandomPeer().findSuccessor(new Key("Bacon"));
		
		// The selected peer dies
		peer.die();
		Thread.sleep(2000);
		
		// Make a map that contains missing data
		Map<String, String> maMap = new HashMap<String, String>();
		maMap.putAll(tracker.getRandomPeer().findMissingData());
		
		Thread.sleep(2000);
		
		// Reinject the data in the network
		tracker.getRandomPeer().reinjectData(maMap);
		Thread.sleep(2000);
		
		for (String s : maMap.keySet()){
			assertEquals(maMap.get(s), tracker.getRandomPeer().findSuccessor(new Key(s)).get(s));
		}
	}
	
	// createNetwork from Main class
	//
	public static void createNetwork(Tracker tracker) 
			throws RemoteException, AlreadyRegisteredException, InterruptedException {
		for (int i = 0 ; i < NB_PEERS ; i++) {
			Peer p = new PeerImpl(new Identifier(i * 100));

			if (i == 0) {
				System.out.println("Ring created by " + p.getId());
				p.create();
			} 
			else {
				// The new peer is inserted in the network using a random peer 
				// that already belongs to the network. This random peer is 
				// retrieved thanks to the tracker.
				Peer randomPeer = tracker.getRandomPeer();
				System.out.println("Added " + p.getId() + " from "
						+ randomPeer.getId() + " that points to "
						+ randomPeer.getSuccessor().getId());
				p.join(randomPeer);
			}

			tracker.register(p);
		}
	}
	
	// turnAround from Main class
	//
	private static void turnAround(Peer landmarkPeer) throws RemoteException {
		System.out.println(
				"\nStarted turn around from " + landmarkPeer.getId());
		Peer nextPeer = landmarkPeer;

		do {
			nextPeer = nextPeer.getSuccessor();
			System.out.println("Visited " + nextPeer.describe());

		} while (!nextPeer.equals(landmarkPeer));
	}
}